package gin

import (
	"errors"
	"fmt"
	"gitee.com/gopher2011/gin/bind"
	"gitee.com/gopher2011/gin/render"
	"gitee.com/gopher2011/gin/sse"
	"io"
	"io/ioutil"
	"math"
	"mime/multipart"
	"net"
	"net/http"
	"net/url"
	"os"
	"strings"
	"sync"
	"time"
)

// 最常见的数据格式的 Content-Type MIME。
const (
	MiMeJSON              = bind.MiMeJSON
	MiMeHTML              = bind.MiMeHTML
	MiMeXML               = bind.MiMeXML
	MiMeXML2              = bind.MiMeXML2
	MiMePlain             = bind.MiMePlain
	MiMePOSTForm          = bind.MiMePOSTForm
	MiMeMultipartPOSTForm = bind.MiMeMultipartPOSTForm
	MiMeYAML              = bind.MiMeYAML
)

const (
	// BodyBytesKey 表示默认的主体字节密钥。
	BodyBytesKey = "_gin-gonic/gin/bodybyteskey"
	abortIndex int8 = math.MaxInt8 / 2
)

type (
	// HandlerFunc 是(http.ResponseWriter,*http.Request)函数签名的扩展。
	HandlerFunc func(*Context)
	// HandlersChain 是 HandlerFunc 的集合。
	//  即: HandlersChain 是一个容器，里面放的都是 func(*Context)。
	HandlersChain []HandlerFunc
)

// Context 封装 *http.Request 和 http.ResponseWriter。
//  1、请求和响应相关的逻辑都由 Context 模块承载。
//
//  2、获取 URL路径 中的请求参数: Param() Query() QueryDefault() QueryArray() QueryMap() 以及 GetXXX()系列方法。这些方法可以用在 GET、POST 请求中。
//
//  3、获取 Request.Body表单 中的请求参数: PostForm() PostFormDefault() PostFormArray() PostFormMap() FormFile() FormMultipart()。这些方法都不能用在 GET 请求中。
//
//  4、解析 请求参数到结构体中(只有3个): Parse() ParseBody() ParseUri()
type Context struct {
	Request    *http.Request // go 标准库中的 *http.Request
	Writer     ResponseWriter
	Params     Params
	Keys       map[string]interface{} // Keys 键是专门用于每个请求上下文的键值对。
	Errors     errorMsgs              // Errors 使用此上下文的所有处理程序中间件附带的错误列表。
	Accepted   []string               // Accepted 用于内容协商的手动接受格式的列表。
	handlers   HandlersChain
	writer     responseWriter // 自定义的 responseWriter 结构体
	index      int8
	fullPath   string
	engine     *Engine
	params     *Params
	mu         sync.RWMutex  // 该互斥锁保护键映射
	queryCache url.Values    // queryCache 使用url.ParseQuery从c.Request.URL.Query（）缓存参数查询结果
	formCache  url.Values    // formCache 使用url.ParseQuery缓存的PostForm包含从POST，PATCH或PUT正文参数解析的表单数据。
	sameSite   http.SameSite // SameSite允许服务器定义cookie属性，从而使浏览器无法将cookie与跨站点请求一起发送。
}

/************************************/
/********** CONTEXT CREATION ********/
/************************************/

func (c *Context) reset() {
	c.Writer = &c.writer
	c.Params = c.Params[0:0]
	c.handlers = nil
	c.index = -1
	c.fullPath = ""
	c.Keys = nil
	c.Errors = c.Errors[0:0]
	c.Accepted = nil
	c.queryCache = nil
	c.formCache = nil
	*c.params = (*c.params)[0:0]
}

// Copy 返回当前 *Context 的副本，该副本可以在请求范围之外安全地使用。
//  当 *Context 必须传递给 goroutine 时，必须使用此方法。
func (c *Context) Copy() *Context {
	cp := Context{
		writer: c.writer,
		Request:   c.Request,
		Params:    c.Params,
		engine:    c.engine,
	}
	cp.writer.ResponseWriter = nil
	cp.Writer = &cp.writer
	cp.index = abortIndex
	cp.handlers = nil
	cp.Keys = map[string]interface{}{}
	for k, v := range c.Keys {
		cp.Keys[k] = v
	}
	paramCopy := make([]Param, len(cp.Params))
	copy(paramCopy, cp.Params)
	cp.Params = paramCopy
	return &cp
}

// Last 返回 HandlersChain 链(请求处理链)中的最后一个 HandlerFunc (请求处理程序)，最后一个 HandlerFunc (请求处理程序)是主要的处理程序。
func (c HandlersChain) Last() HandlerFunc {
	if length := len(c); length > 0 {
		return c[length-1]
	}
	return nil
}

// HandlerName 返回主处理程序的名称。例如，如果处理程序为"handleGetUsers()"，则此函数将返回"main.handleGetUsers"。
func (c *Context) HandlerName() string {
	return nameOfFunction(c.handlers.Last())
}

// HandlerNames 按照 HandlerName()的语义，以降序返回此 *Context 的所有已注册处理程序的列表。
func (c *Context) HandlerNames() []string {
	hn := make([]string, 0, len(c.handlers))
	for _, val := range c.handlers {
		hn = append(hn, nameOfFunction(val))
	}
	return hn
}

// Handler 返回主处理程序。
func (c *Context) Handler() HandlerFunc {
	return c.handlers.Last()
}

// FullPath 返回匹配的路由完整路径。对于未找到的路由，返回一个空字符串。
//     router.GET("/user/:id", func(c *gin.Context) {
//         c.FullPath() == "/user/:id"  // true
//     })
func (c *Context) FullPath() string {
	return c.fullPath
}

/************************************/
/*********** FLOW CONTROL ***********/
/************************************/

// Next 这个方法只在中间件内部使用。
//  它在调用处理程序内的链中执行挂起的处理程序。
//  See example in GitHub.
func (c *Context) Next() {
	c.index++
	for c.index < int8(len(c.handlers)) {
		c.handlers[c.index](c)
		c.index++
	}
}

// IsAborted 如果当前 *Context 中止，则返回true。
func (c *Context) IsAborted() bool {
	return c.index >= abortIndex
}

// Abort 防止挂起的处理程序被调用。请注意，这不会停止当前的处理程序。
//  假设您有一个授权中间件，用于验证当前请求是否得到授权。
//  如果授权失败（例如：密码不匹配），则调用Abort以确保不调用此请求的其余处理程序。
func (c *Context) Abort() {
	c.index = abortIndex
}

// AbortWithStatus 调用 Abort() 并使用指定的状态代码写入标头。
//  例如，对请求进行身份验证的失败尝试可以使用：context.AbortWithStatus(401)。
func (c *Context) AbortWithStatus(code int) {
	c.Status(code)
	c.Writer.WriteHeaderNow()
	c.Abort()
}

// AbortWithStatusJSON 在内部调用 *Context.Abort()，然后调用 *Context.JSON()。
//  此方法停止链，写入状态代码并返回JSON正文。还将 Content-Type 设置为" application/json"。
func (c *Context) AbortWithStatusJSON(code int, jsonObj interface{}) {
	c.Abort()
	c.JSON(code, jsonObj)
}

// AbortWithError 内部调用 *Context.AbortWithStatus() 和 *Context.Error()。
//  此方法停止链，写入状态代码，并将指定的错误推送到 *Context.Error()。有关更多详细信息，请参见 Context.Error()。
func (c *Context) AbortWithError(code int, err error) *Error {
	c.AbortWithStatus(code)
	return c.Error(err)
}

/************************************/
/********* ERROR MANAGEMENT *********/
/************************************/

// Error 将错误附加到当前 *Context。错误被推送到错误列表。
//  对于请求解析期间发生的每个错误，最好都调用 Error。
//  中间件可用于收集所有错误并将它们一起推送到数据库，打印日志或将其附加到HTTP响应中。
//  如果err为nil，err 将 panic。
func (c *Context) Error(err error) *Error {
	if err == nil {
		panic("err is nil")
	}

	parsedError, ok := err.(*Error)
	if !ok {
		parsedError = &Error{
			Err:  err,
			Type: ErrorTypePrivate,
		}
	}

	c.Errors = append(c.Errors, parsedError)
	return parsedError
}

/************************************/
/******** Metadata Management********/
/************************************/

// Set 用于为此 *Context 专门存储新的键值对。
//  如果以前没有使用过c.Keys，它也会延迟初始化。
func (c *Context) Set(key string, value interface{}) {
	c.mu.Lock()
	if c.Keys == nil {
		c.Keys = make(map[string]interface{})
	}

	c.Keys[key] = value
	c.mu.Unlock()
}

// Get 返回给定键的值，即：（值，true）。
//  如果该值不存在，则返回（nil，false）
func (c *Context) Get(key string) (value interface{}, exists bool) {
	c.mu.RLock()
	value, exists = c.Keys[key]
	c.mu.RUnlock()
	return
}

// MustGet 返回给定键的值（如果存在），否则会 panic。
func (c *Context) MustGet(key string) interface{} {
	if value, exists := c.Get(key); exists {
		return value
	}
	panic("Key \"" + key + "\" does not exist")
}

// GetString 以字符串形式返回与键关联的值。
func (c *Context) GetString(key string) (s string) {
	if val, ok := c.Get(key); ok && val != nil {
		s, _ = val.(string)
	}
	return
}

// GetBool 返回与键关联的值作为布尔值。
func (c *Context) GetBool(key string) (b bool) {
	if val, ok := c.Get(key); ok && val != nil {
		b, _ = val.(bool)
	}
	return
}

// GetInt 以整数形式返回与键关联的值。
func (c *Context) GetInt(key string) (i int) {
	if val, ok := c.Get(key); ok && val != nil {
		i, _ = val.(int)
	}
	return
}

// GetInt64 returns the value associated with the key as an integer.
func (c *Context) GetInt64(key string) (i64 int64) {
	if val, ok := c.Get(key); ok && val != nil {
		i64, _ = val.(int64)
	}
	return
}

// GetUint returns the value associated with the key as an unsigned integer.
func (c *Context) GetUint(key string) (ui uint) {
	if val, ok := c.Get(key); ok && val != nil {
		ui, _ = val.(uint)
	}
	return
}

// GetUint64 returns the value associated with the key as an unsigned integer.
func (c *Context) GetUint64(key string) (ui64 uint64) {
	if val, ok := c.Get(key); ok && val != nil {
		ui64, _ = val.(uint64)
	}
	return
}

// GetFloat64 returns the value associated with the key as a float64.
func (c *Context) GetFloat64(key string) (f64 float64) {
	if val, ok := c.Get(key); ok && val != nil {
		f64, _ = val.(float64)
	}
	return
}

// GetTime returns the value associated with the key as time.
func (c *Context) GetTime(key string) (t time.Time) {
	if val, ok := c.Get(key); ok && val != nil {
		t, _ = val.(time.Time)
	}
	return
}

// GetDuration returns the value associated with the key as a duration.
func (c *Context) GetDuration(key string) (d time.Duration) {
	if val, ok := c.Get(key); ok && val != nil {
		d, _ = val.(time.Duration)
	}
	return
}

// GetStringSlice returns the value associated with the key as a slice of strings.
func (c *Context) GetStringSlice(key string) (ss []string) {
	if val, ok := c.Get(key); ok && val != nil {
		ss, _ = val.([]string)
	}
	return
}

// GetStringMap returns the value associated with the key as a map of interfaces.
func (c *Context) GetStringMap(key string) (sm map[string]interface{}) {
	if val, ok := c.Get(key); ok && val != nil {
		sm, _ = val.(map[string]interface{})
	}
	return
}

// GetStringMapString returns the value associated with the key as a map of strings.
func (c *Context) GetStringMapString(key string) (sms map[string]string) {
	if val, ok := c.Get(key); ok && val != nil {
		sms, _ = val.(map[string]string)
	}
	return
}

// GetStringMapStringSlice returns the value associated with the key as a map to a slice of strings.
func (c *Context) GetStringMapStringSlice(key string) (smss map[string][]string) {
	if val, ok := c.Get(key); ok && val != nil {
		smss, _ = val.(map[string][]string)
	}
	return
}

/************************************/
/************ Input Data ************/
/************************************/

// Param 获取URL路径中的 URL参数。
//  1、既可以用在GET请求中，也可以用在POST请求中。
//
//  2、如果存在，则返回<key>的URL的参数值，否则返回一个空字符串。
//
//  3、注意:这并不是获取查询参数。
//
//  router.GET("/user/:id", func(c *gin.Context) {
//         id := c.Param("id")
//  })
//
//  详细文档: https://www.kancloud.cn/shuangdeyu/gin_book/949416
func (c *Context) Param(key string) string {
	return c.Params.ByName(key)
}

// Query: 获取URL路径中的 查询参数。
//  1、既可以用在GET请求中，也可以用在POST请求中。
//
//  2、如果存在，则返回<key>的URL查询值，否则返回一个空字符串。
//
//  3、注意:这并不是获取URL参数。
//
//  GET /path?id=1234&name=Manu&value=
// 	c.Query("id") == "1234"
// 	c.Query("name") == "Manu"
// 	c.Query("value") == ""
// 	c.Query("wtf") == ""
func (c *Context) Query(key string) string {
	value, _ := c.getQuery(key)
	return value
}

// QueryDefault 获取 GET 请求中的参数。返回键入的url查询值（如果存在），否则返回指定的defaultValue字符串。
//
//  例如:
//     GET /?name=Manu&lastname=
//     c.DefaultQuery("name", "unknown") == "Manu"
//     c.DefaultQuery("id", "none") == "none"
//     c.DefaultQuery("lastname", "none") == ""
//
//  详细文档: https://www.kancloud.cn/shuangdeyu/gin_book/949417
func (c *Context) QueryDefault(key, defaultValue string) string {
	if value, ok := c.getQuery(key); ok {
		return value
	}
	return defaultValue
}

// getQuery 就像Query()一样。
//  如果存在(value,true)（即使该值是一个空字符串），它也会返回键控的url查询值，否则将返回(" ",false)。
//  这是 *Context.Request.URL.Query().Get(key) 的快捷方式。
//     GET /?name=Manu&lastname=
//     ("Manu", true) == c.GetQuery("name")
//     ("", false) == c.GetQuery("id")
//     ("", true) == c.GetQuery("lastname")
func (c *Context) getQuery(key string) (string, bool) {
	if values, ok := c.getQueryArray(key); ok {
		return values[0], ok
	}
	return "", false
}

// QueryArray 获取 GET 请求中的参数。返回给定查询键的字符串切片。切片的长度取决于给定关键字的参数数量。
//  URL查询参数，就是一个数组，例如: URL是这样 ?a=b&a=c&a=d，key值都一样，但是对应的value不一样。
//
//  例如:
//      URL: /?media=blog&media=wechat，它们的 key 都是 media。
//
//      返回: ["blog","wechat"]
//
//  详细文档: https://www.flysnow.org/2019/12/18/golang-gin-query-parameters-array-map.html
func (c *Context) QueryArray(key string) []string {
	values, _ := c.getQueryArray(key)
	return values
}

// QueryMap 获取 GET 请求中的参数。返回给定查询键的字符串字典。
//  其实就是把满足一定格式的URL查询参数，转换为一个map。
//
//  例如:
//      URL: /?ids[a]=123&ids[b]=456&ids[c]=789
//
//      返回: {"a":"123","b":"456","c":"789"}
//
//  详细文档: https://www.flysnow.org/2019/12/18/golang-gin-query-parameters-array-map.html
func (c *Context) QueryMap(key string) map[string]string {
	dict, _ := c.getQueryMap(key)
	return dict
}

func (c *Context) initQueryCache() {
	if c.queryCache == nil {
		if c.Request != nil {
			c.queryCache = c.Request.URL.Query()
		} else {
			c.queryCache = url.Values{}
		}
	}
}

// GetQueryArray returns a slice of strings for a given query key, plus
// a boolean value whether at least one value exists for the given key.
func (c *Context) getQueryArray(key string) ([]string, bool) {
	c.initQueryCache()
	if values, ok := c.queryCache[key]; ok && len(values) > 0 {
		return values, true
	}
	return []string{}, false
}

// GetQueryMap returns a map for a given query key, plus a boolean value
// whether at least one value exists for the given key.
func (c *Context) getQueryMap(key string) (map[string]string, bool) {
	c.initQueryCache()
	return c.get(c.queryCache, key)
}

// PostForm 获取 POST 请求中的表单参数。注意: 这个方法不能用于 GET 请求。
//  如果存在，则以 POST url/encoded 形式或多部分形式返回 key对应的value，否则返回一个空字符串。
//
//  详细文档: https://www.kancloud.cn/shuangdeyu/gin_book/949418
func (c *Context) PostForm(key string) string {
	value, _ := c.getPostForm(key)
	return value
}

// PostFormDefault 获取 POST 请求中的参数。注意: 这个方法不能用于 GET 请求。
//  如果存在，则以 POST urlencoded 形式或多部分形式返回 key对应的value，否则返回指定的 defaultValue 字符串。
func (c *Context) PostFormDefault(key, defaultValue string) string {
	if value, ok := c.getPostForm(key); ok {
		return value
	}
	return defaultValue
}

// getPostForm 就像 *Context.PostForm(key)。
//  当它存在 (value，true)（即使值是一个空字符串）时，它从POST url/encode形式或多部分形式返回指定的键，否则返回(" ",false)。
//  例如，在PATCH请求中更新用户的电子邮件期间:
//     email=mail@example.com  -->  ("mail@example.com", true) := GetPostForm("email") // set email to "mail@example.com"
// 	   email=                  -->  ("", true) := GetPostForm("email") // set email to ""
//                             -->  ("", false) := GetPostForm("email") // do nothing with email
func (c *Context) getPostForm(key string) (string, bool) {
	if values, ok := c.getPostFormArray(key); ok {
		return values[0], ok
	}
	return "", false
}

// PostFormArray 获取 POST 请求中的参数。注意: 这个方法不能用于 GET 请求。
//  返回给定表单键的字符串切片。切片的长度取决于具有给定键的参数的数量。
func (c *Context) PostFormArray(key string) []string {
	values, _ := c.getPostFormArray(key)
	return values
}

// PostFormMap 获取 POST 请求中的参数。注意: 这个方法不能用于 GET 请求。
//  返回给定表单键的映射。
func (c *Context) PostFormMap(key string) map[string]string {
	dict, _ := c.getPostFormMap(key)
	return dict
}

func (c *Context) initFormCache() {
	if c.formCache == nil {
		c.formCache = make(url.Values)
		req := c.Request
		if err := req.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil {
			if err != http.ErrNotMultipart {
				debugPrint("error on parse multipart form array: %v", err)
			}
		}
		c.formCache = req.PostForm
	}
}

// GetPostFormArray 返回给定形式键的字符串切片，以及一个布尔值（无论给定键是否存在至少一个值）。
func (c *Context) getPostFormArray(key string) ([]string, bool) {
	c.initFormCache()
	if values := c.formCache[key]; len(values) > 0 {
		return values, true
	}
	return []string{}, false
}

// GetPostFormMap 返回给定表单键的映射，以及一个布尔值（无论该给定键是否存在至少一个值），然后返回一个布尔值。
func (c *Context) getPostFormMap(key string) (map[string]string, bool) {
	c.initFormCache()
	return c.get(c.formCache, key)
}

// get 是一种内部方法，并返回满足条件的map。
func (c *Context) get(m map[string][]string, key string) (map[string]string, bool) {
	dict := make(map[string]string)
	exist := false
	for k, v := range m {
		if i := strings.IndexByte(k, '['); i >= 1 && k[0:i] == key {
			if j := strings.IndexByte(k[i+1:], ']'); j >= 1 {
				exist = true
				dict[k[i+1:][:j]] = v[0]
			}
		}
	}
	return dict, exist
}

// FormFile 获取 POST 请求中的单个文件。每次只能获取一个文件。这个方法不能用于 GET 请求。
//  详细文档: https://www.kancloud.cn/shuangdeyu/gin_book/949420
func (c *Context) FormFile(name string) (*multipart.FileHeader, error) {
	if c.Request.MultipartForm == nil {
		if err := c.Request.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil {
			return nil, err
		}
	}
	f, fh, err := c.Request.FormFile(name)
	if err != nil {
		return nil, err
	}
	f.Close()
	return fh, err
}

// FormMultipart (原MultipartForm) 获取 POST 请求中的多部分表单，包括多个文件上传。每次能获取多个文件。
//  详细文档: https://www.kancloud.cn/shuangdeyu/gin_book/949420
func (c *Context) FormMultipart() (*multipart.Form, error) {
	err := c.Request.ParseMultipartForm(c.engine.MaxMultipartMemory)
	return c.Request.MultipartForm, err
}

// SaveUploadedFile 将表单文件上传到特定的 <dst>。
func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error {
	src, err := file.Open()
	if err != nil {
		return err
	}
	defer src.Close()

	out, err := os.Create(dst)
	if err != nil {
		return err
	}
	defer out.Close()

	_, err = io.Copy(out, src)
	return err
}

// bind (原Bind方法) 检查 Content-Type 以自动选择绑定引擎。根据 "Content-Type" 标头，使用不同的绑定:
//     "application/json" --> JSON binding
//     "application/xml"  --> XML binding
//  否则->返回错误。
//  如果 Content-Type == "application/json"使用JSON或XML作为JSON输入，它将请求的主体解析为JSON格式的数据。
//  它将json有效负载解码为指定为指针的结构。
//  如果输入无效，它将写入400错误并在响应中设置 Content-Type 标头 "text/plain"
//  此方法不够灵活，建议使用 Parse 方法。
func (c *Context) bind(obj interface{}) error {
	b := bind.Default(c.Request.Method, c.ContentType())
	return c.mustBindWith(obj, b)
}

// BindJSON is a shortcut for c.MustBindWith(obj, binding.JSON).
func (c *Context) bindJSON(obj interface{}) error {
	return c.mustBindWith(obj, bind.JSON)
}

// BindXML is a shortcut for c.MustBindWith(obj, binding.BindXML).
func (c *Context) bindXML(obj interface{}) error {
	return c.mustBindWith(obj, bind.XML)
}

// BindQuery is a shortcut for c.MustBindWith(obj, binding.Query).
func (c *Context) bindQuery(obj interface{}) error {
	return c.mustBindWith(obj, bind.Query)
}

// BindYAML is a shortcut for c.MustBindWith(obj, binding.YAML).
func (c *Context) bindYAML(obj interface{}) error {
	return c.mustBindWith(obj, bind.YAML)
}

// BindHeader is a shortcut for c.MustBindWith(obj, binding.Header).
func (c *Context) bindHeader(obj interface{}) error {
	return c.mustBindWith(obj, bind.Header)
}

// bindUri (原BindUri方法) 使用binding.Uri绑定传递的struct指针。
//  如果发生任何错误，它将使用 HTTP 400终止请求。
func (c *Context) bindUri(obj interface{}) error {
	if err := c.ParseUri(obj); err != nil {
		c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) // nolint: errcheck
		return err
	}
	return nil
}

// mustBindWith (原MustBindWith方法)使用指定的绑定引擎绑定传递的struct指针。
//  如果发生任何错误，它将使用HTTP 400终止请求。此方法不够灵活，不建议使用。
func (c *Context) mustBindWith(obj interface{}, b bind.IBind) error {
	if err := c.parseOnce(obj, b); err != nil {
		c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) // nolint: errcheck
		return err
	}
	return nil
}

// ParseUri (ShouldBindUri) 将 request(请求)中的uri数据解析到obj中。
func (c *Context) ParseUri(obj interface{}) error {
	m := make(map[string][]string)
	for _, v := range c.Params {
		m[v.Key] = []string{v.Value}
	}
	return bind.Uri.BindUri(m, obj)
}

// Parse (原ShouldBind方法)将请求参数解析到<obj>中，参数<obj>必须是指针类型!
//  1、检查 Content-Type 以自动选择解析引擎。根据 "Content-Type"标头，使用不同的解析器:
//     "application/json" --> JSON bind
//     "application/xml"  --> XML bind
//  否则->返回错误。
//
//  2、如果 Content-Type == " application/json" 则:它将请求的主体解析为JSON格式的数据。
//
//  3、保存在 <obj>中的请求参数，不能重用。该方法使用一次后，<obj>中的数据就被清空了。
// 	   如果要调用多次，并且要重用<obj>的请求参数，请使用 ParseBody
func (c *Context) Parse(obj interface{}) error {
	b := bind.Default(c.Request.Method, c.ContentType())
	return c.parseOnce(obj, b)
}

// ParseBody 将请求参数解析到<obj>中，适用于重复解析。参数<obj>必须是指针类型!
//  1、ParseBody 与 Parse 相似，唯一区别: ParseBody 会将请求正文存储到上下文中，并在再次调用时重用。
//
//  2、注意: 此方法在解析之前读取正文。因此，如果只需要调用一次，则应使用 Parse 以获得更好的性能。
//  (原ShouldBindBodyWith方法)测试用例 https://www.jianshu.com/p/0f0bf53bedd2
func (c *Context) ParseBody(obj interface{}) (err error){
	var body []byte
	if cb, ok := c.Get(BodyBytesKey); ok {
		if cbb, ok := cb.([]byte); ok {
			body = cbb
		}
	}
	if body == nil {
		body, err = ioutil.ReadAll(c.Request.Body)
		if err != nil {
			return err
		}
		c.Set(BodyBytesKey, body)
	}
	b := bind.Default(c.Request.Method, c.ContentType())
	bb := bind.DefaultBody(b.Name())
	return bb.BindBody(body, obj)
}

// ContentType 返回请求的Content-Type标头。
func (c *Context) ContentType() string {
	return filterFlags(c.requestHeader("Content-Type"))
}

// parseOnce (原ShouldBindWith方法) 使用指定的绑定引擎绑定传递的struct指针。
//  1、用户可以自行选择绑定器，自行对出错处理。自行选择绑定器，这也意味着用户可以自己实现绑定器。
//  例如: 嫌弃默认的json处理是用官方的json处理包，嫌弃它慢，可以自己实现Binding接口。
//
//  2、保存在 <obj>中的请求参数，不能重用。该方法使用一次后，<obj>中的数据就被清空了。
//  如果要调用多次，并且要重用<obj>的请求参数，请使用 ParseBody
func (c *Context) parseOnce(obj interface{}, b bind.IBind) error {
	return b.Bind(c.Request, obj)
}

// parseBody (原ShouldBindBodyWith方法) 将请求参数解析到<obj>中，适用于重复绑定。
//  1、parseBody与 parseOnce 相似，唯一区别: parseBody 会将请求正文存储到上下文中，并在再次调用时重用。
//
//  2、注意: 此方法在绑定之前读取正文。因此，如果只需要调用一次，则应使用 parseOnce 以获得更好的性能。
//
func (c *Context) parseBody(obj interface{}, bb bind.IBindBody) (err error) {
	var body []byte
	if cb, ok := c.Get(BodyBytesKey); ok {
		if cbb, ok := cb.([]byte); ok {
			body = cbb
		}
	}
	if body == nil {
		body, err = ioutil.ReadAll(c.Request.Body)
		if err != nil {
			return err
		}
		c.Set(BodyBytesKey, body)
	}
	return bb.BindBody(body, obj)
}

// ShouldBindJSON is a shortcut for c.ShouldBindWith(obj, binding.JSON).
func (c *Context) shouldBindJSON(obj interface{}) error {
	return c.parseOnce(obj, bind.JSON)
}

// ShouldBindXML is a shortcut for c.ShouldBindWith(obj, binding.XML).
func (c *Context) shouldBindXML(obj interface{}) error {
	return c.parseOnce(obj, bind.XML)
}

// ShouldBindQuery is a shortcut for c.ShouldBindWith(obj, binding.Query).
func (c *Context) shouldBindQuery(obj interface{}) error {
	return c.parseOnce(obj, bind.Query)
}

// ShouldBindYAML is a shortcut for c.ShouldBindWith(obj, binding.YAML).
func (c *Context) shouldBindYAML(obj interface{}) error {
	return c.parseOnce(obj, bind.YAML)
}

// ShouldBindHeader is a shortcut for c.ShouldBindWith(obj, binding.Header).
func (c *Context) shouldBindHeader(obj interface{}) error {
	return c.parseOnce(obj, bind.Header)
}

// ClientIP 以返回真实的客户端IP
// 它在后台调用了c.RemoteIP()，以检查远程IP是否是受信任的代理。
// 如果是，它将尝试解析在Engine.RemoteIPHeaders中定义的标头（默认为[X-Forwarded-For，X-Real-Ip]）。
// 如果标头在语法上无效，或者远程IP与信任的代理不对应，则返回远程IP（来自Request.RemoteAddr）。
func (c *Context) ClientIP() string {
	if c.engine.AppEngine {
		if addr := c.requestHeader("X-Appengine-Remote-Addr"); addr != "" {
			return addr
		}
	}

	remoteIP, trusted := c.RemoteIP()
	if remoteIP == nil {
		return ""
	}

	if trusted && c.engine.ForwardedByClientIP && c.engine.RemoteIPHeaders != nil {
		for _, headerName := range c.engine.RemoteIPHeaders {
			ip, valid := validateHeader(c.requestHeader(headerName))
			if valid {
				return ip
			}
		}
	}
	return remoteIP.String()
}

// RemoteIP parses the IP from Request.RemoteAddr, normalizes and returns the IP (without the port).
// It also checks if the remoteIP is a trusted proxy or not.
// In order to perform this validation, it will see if the IP is contained within at least one of the CIDR blocks
// defined in Engine.TrustedProxies
func (c *Context) RemoteIP() (net.IP, bool) {
	ip, _, err := net.SplitHostPort(strings.TrimSpace(c.Request.RemoteAddr))
	if err != nil {
		return nil, false
	}
	remoteIP := net.ParseIP(ip)
	if remoteIP == nil {
		return nil, false
	}

	if c.engine.trustedCIDRs != nil {
		for _, cidr := range c.engine.trustedCIDRs {
			if cidr.Contains(remoteIP) {
				return remoteIP, true
			}
		}
	}

	return remoteIP, false
}

func validateHeader(header string) (clientIP string, valid bool) {
	if header == "" {
		return "", false
	}
	items := strings.Split(header, ",")
	for i, ipStr := range items {
		ipStr = strings.TrimSpace(ipStr)
		ip := net.ParseIP(ipStr)
		if ip == nil {
			return "", false
		}

		// We need to return the first IP in the list, but,
		// we should not early return since we need to validate that
		// the rest of the header is syntactically valid
		if i == 0 {
			clientIP = ipStr
			valid = true
		}
	}
	return
}



// IsWebsocket 如果请求标头指示客户端正在发起 Websocket 握手，则返回true。
func (c *Context) IsWebsocket() bool {
	if strings.Contains(strings.ToLower(c.requestHeader("Connection")), "upgrade") &&
		strings.EqualFold(c.requestHeader("Upgrade"), "websocket") {
		return true
	}
	return false
}

func (c *Context) requestHeader(key string) string {
	return c.Request.Header.Get(key)
}

/************************************/
/******** 响应==>渲染 ********/
/************************************/

// bodyAllowedForStatus is a copy of http.bodyAllowedForStatus non-exported function.
func bodyAllowedForStatus(status int) bool {
	switch {
	case status >= 100 && status <= 199:
		return false
	case status == http.StatusNoContent:
		return false
	case status == http.StatusNotModified:
		return false
	}
	return true
}

// Status 设置HTTP响应代码。
func (c *Context) Status(code int) {
	c.Writer.WriteHeader(code)
}

// Header 是 *Context.Writer.Header().Set(key，value) 的智能快捷方式。
//  它在响应中写入标头。如果value ==" "，则此方法删除标头 *Context.Writer.Header().Del(key)
func (c *Context) Header(key, value string) {
	if value == "" {
		c.Writer.Header().Del(key)
		return
	}
	c.Writer.Header().Set(key, value)
}

// GetHeader 从请求标头返回值。
func (c *Context) GetHeader(key string) string {
	return c.requestHeader(key)
}

// GetRawData 返回流数据。其内部代码 ioutil.ReadAll(c.Request.Body)
func (c *Context) GetRawData() ([]byte, error) {
	return ioutil.ReadAll(c.Request.Body)
}

// SetSameSite with cookie
func (c *Context) SetSameSite(samesite http.SameSite) {
	c.sameSite = samesite
}

// SetCookie 将 Set-Cookie 标头添加到 ResponseWriter 的标头中。
//  提供的cookie必须具有有效的名称。无效的cookie可能会被静默删除。
func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool) {
	if path == "" {
		path = "/"
	}
	http.SetCookie(c.Writer, &http.Cookie{
		Name:     name,
		Value:    url.QueryEscape(value),
		MaxAge:   maxAge,
		Path:     path,
		Domain:   domain,
		SameSite: c.sameSite,
		Secure:   secure,
		HttpOnly: httpOnly,
	})
}

// Cookie 返回请求中提供的命名 cookie，如果找不到，则返回ErrNoCookie。
// 并返回已命名的cookie，并且不对其进行转义。如果多个Cookie与给定名称匹配，则仅返回一个Cookie。
func (c *Context) Cookie(name string) (string, error) {
	cookie, err := c.Request.Cookie(name)
	if err != nil {
		return "", err
	}
	val, _ := url.QueryUnescape(cookie.Value)
	return val, nil
}

// Render 编写响应标头并调用 render.Render() 渲染数据。
func (c *Context) Render(code int, r render.Render) {
	c.Status(code)

	if !bodyAllowedForStatus(code) {
		r.WriteContentType(c.Writer)
		c.Writer.WriteHeaderNow()
		return
	}

	if err := r.Render(c.Writer); err != nil {
		panic(err)
	}
}

// HTML 渲染由其文件名指定的HTTP模板。
//  它还会更新HTTP代码，并将 Content-Type 设置为 "text/html"。
//  See http://golang.org/doc/articles/wiki/
func (c *Context) HTML(code int, name string, obj interface{}) {
	instance := c.engine.HTMLRender.Instance(name, obj)
	c.Render(code, instance)
}

// IndentedJSON 将给定结构体序列化为漂亮的JSON（缩进+结束行）到响应主体中。
//  还将 Content-Type设置为 "application/json"。
//  警告：我们建议仅将其用于开发目的，因为打印漂亮的JSON会占用更多的CPU和带宽。请改用 Context.JSON()。
func (c *Context) IndentedJSON(code int, obj interface{}) {
	c.Render(code, render.IndentedJSON{Data: obj})
}

// SecureJSON 将给定的结构体作为Secure JSON序列化到响应主体中。
// 如果给定的结构体是数组值，则默认值在响应主体前加上 "while(1)"。还将 Content-Type 设置为 "application/json"。
func (c *Context) SecureJSON(code int, obj interface{}) {
	c.Render(code, render.SecureJSON{Prefix: c.engine.secureJSONPrefix, Data: obj})
}

// JSONP 将给定结构体作为JSON序列化到响应主体中。
//  它将填充添加到响应主体，以从位于与客户端不同的域中的服务器请求数据。
//  还将 Content-Type 设置为 "application/javascript"。
func (c *Context) JSONP(code int, obj interface{}) {
	callback := c.QueryDefault("callback", "")
	if callback == "" {
		c.Render(code, render.JSON{Data: obj})
		return
	}
	c.Render(code, render.JsonpJSON{Callback: callback, Data: obj})
}

// JSON 将给定结构体作为JSON序列化到响应主体中。
//  还将 Content-Type 设置为 "application/json"。
func (c *Context) JSON(code int, obj interface{}) {
	c.Render(code, render.JSON{Data: obj})
}

// AsciiJSON 使用 Unicode 到 ASCII 字符串将给定结构体以JSON序列化到响应主体中。
//  还将 Content-Type 设置为 "application/json"。
func (c *Context) AsciiJSON(code int, obj interface{}) {
	c.Render(code, render.AsciiJSON{Data: obj})
}

// PureJSON 将给定结构体作为JSON序列化到响应主体中。
//  *Context.PureJSON() 与 *Context.JSON() 不同，PureJSON 不会用其unicode实体替换特殊的html字符。
func (c *Context) PureJSON(code int, obj interface{}) {
	c.Render(code, render.PureJSON{Data: obj})
}

// XML 将给定的结构体作为XML序列化到响应主体中。
//  还将 Content-Type 设置为 "application/xml"。
func (c *Context) XML(code int, obj interface{}) {
	c.Render(code, render.XML{Data: obj})
}

// YAML 将给定的结构体作为YAML序列化到响应主体中。
func (c *Context) YAML(code int, obj interface{}) {
	c.Render(code, render.YAML{Data: obj})
}

// ProtoBuf 将给定的结构体作为ProtoBuf序列化到响应主体中。
func (c *Context) ProtoBuf(code int, obj interface{}) {
	c.Render(code, render.ProtoBuf{Data: obj})
}

// String 将给定的字符串写入响应主体。
func (c *Context) String(code int, format string, values ...interface{}) {
	c.Render(code, render.String{Format: format, Data: values})
}

// Redirect 返回到特定位置的HTTP重定向。
func (c *Context) Redirect(code int, location string) {
	c.Render(-1, render.Redirect{
		Code:     code,
		Location: location,
		Request:  c.Request,
	})
}

// Data 将一些数据写入主体流并更新HTTP代码。
func (c *Context) Data(code int, contentType string, data []byte) {
	c.Render(code, render.Data{
		ContentType: contentType,
		Data:        data,
	})
}

// DataFromReader 将指定的Render写入主体流并更新HTTP代码。
func (c *Context) DataFromReader(code int, contentLength int64, contentType string, reader io.Reader, extraHeaders map[string]string) {
	c.Render(code, render.Reader{
		Headers:       extraHeaders,
		ContentType:   contentType,
		ContentLength: contentLength,
		Reader:        reader,
	})
}

// File 以一种有效的方式将指定的文件写入主体流。
func (c *Context) File(filepath string) {
	http.ServeFile(c.Writer, c.Request, filepath)
}

// FileFromFS 将http.FileSystem中的指定文件以高效的方式写入主体流。
func (c *Context) FileFromFS(filepath string, fs http.FileSystem) {
	defer func(old string) {
		c.Request.URL.Path = old
	}(c.Request.URL.Path)

	c.Request.URL.Path = filepath

	http.FileServer(fs).ServeHTTP(c.Writer, c.Request)
}

// FileAttachment 以一种有效的方式将指定的文件写入主体流中在客户端，通常将使用给定的文件名下载该文件。
func (c *Context) FileAttachment(filepath, filename string) {
	c.Writer.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename))
	http.ServeFile(c.Writer, c.Request, filepath)
}

// SSEvent 将服务器发送的事件写入主体流。
func (c *Context) SSEvent(name string, message interface{}) {
	c.Render(-1, sse.Event{
		Event: name,
		Data:  message,
	})
}

// Stream 发送流式响应并返回布尔值，指示 "Is client disconnected in middle of stream"。
func (c *Context) Stream(step func(w io.Writer) bool) bool {
	w := c.Writer
	clientGone := w.CloseNotify()
	for {
		select {
		case <-clientGone:
			return true
		default:
			keepOpen := step(w)
			w.Flush()
			if !keepOpen {
				return false
			}
		}
	}
}

/************************************/
/******** CONTENT NEGOTIATION *******/
/************************************/

// Negotiate contains all negotiations data.
type Negotiate struct {
	Offered  []string
	HTMLName string
	HTMLData interface{}
	JSONData interface{}
	XMLData  interface{}
	YAMLData interface{}
	Data     interface{}
}

// Negotiate 根据可接受的接受格式调用不同的渲染器。
func (c *Context) Negotiate(code int, config Negotiate) {
	switch c.NegotiateFormat(config.Offered...) {
	case bind.MiMeJSON:
		data := chooseData(config.JSONData, config.Data)
		c.JSON(code, data)

	case bind.MiMeHTML:
		data := chooseData(config.HTMLData, config.Data)
		c.HTML(code, config.HTMLName, data)

	case bind.MiMeXML:
		data := chooseData(config.XMLData, config.Data)
		c.XML(code, data)

	case bind.MiMeYAML:
		data := chooseData(config.YAMLData, config.Data)
		c.YAML(code, data)

	default:
		c.AbortWithError(http.StatusNotAcceptable, errors.New("the accepted formats are not offered by the server")) // nolint: errcheck
	}
}

// NegotiateFormat 返回可接受的接受格式。
func (c *Context) NegotiateFormat(offered ...string) string {
	assert1(len(offered) > 0, "you must provide at least one offer")

	if c.Accepted == nil {
		c.Accepted = parseAccept(c.requestHeader("Accept"))
	}
	if len(c.Accepted) == 0 {
		return offered[0]
	}
	for _, accepted := range c.Accepted {
		for _, offer := range offered {
			// According to RFC 2616 and RFC 2396, non-ASCII characters are not allowed in headers,
			// therefore we can just iterate over the string without casting it into []rune
			i := 0
			for ; i < len(accepted); i++ {
				if accepted[i] == '*' || offer[i] == '*' {
					return offer
				}
				if accepted[i] != offer[i] {
					break
				}
			}
			if i == len(accepted) {
				return offer
			}
		}
	}
	return ""
}

// SetAccepted 设置接受标头数据。
func (c *Context) SetAccepted(formats ...string) {
	c.Accepted = formats
}

/************************************/
/***** GOLANG.ORG/X/NET/CONTEXT *****/
/************************************/

// Deadline 总是返回没有截止日期（ok == false），也许您想使用 Request.Context().Deadline() 代替。
func (c *Context) Deadline() (deadline time.Time, ok bool) {
	return
}

// Done 总是返回nil (chan将永远等待)，如果要在关闭连接时中止工作，则应改用 Request.Context().Done()。
func (c *Context) Done() <-chan struct{} {
	return nil
}

// Err 总是返回 nil，也许您想使用 Request.Context().Err()代替。
func (c *Context) Err() error {
	return nil
}

// Value 返回与此 *Context 关联的键值；如果没有值与键关联，则返回nil。使用相同的键连续调用Value会返回相同的结果。
func (c *Context) Value(key interface{}) interface{} {
	if key == 0 {
		return c.Request
	}
	if keyAsString, ok := key.(string); ok {
		val, _ := c.Get(keyAsString)
		return val
	}
	return nil
}
// IsAjax 判断当前请求是否是Ajax请求
func (c *Context)IsAjax()bool{
	return strings.EqualFold(c.Request.Header.Get("X-Requested-With"), "XMLHttpRequest")
}

// 处理跨域请求,支持options访问
func(c *Context)Cors()HandlerFunc{
	return func(r *Context) {
		method := r.Request.Method
		r.Header("Access-Control-Allow-Origin", "*")
		r.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token,Authorization,Token,X-Token,X-User-Id")
		r.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS,DELETE,PUT")
		r.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type")
		r.Header("Access-Control-Allow-Credentials", "true")
		if method == "OPTIONS" {// 放行所有OPTIONS方法
			r.AbortWithStatus(http.StatusNoContent)
		}
		r.Next()// 处理请求
	}
}